#ifndef _TBMAT4X4_H_
#define _TBMAT4X4_H_

#include "TBSettings.h"

#include "TBMath.h"
#include "TBVec3.h"

namespace tb
{
	
	class Quat;
	class Mat3x3;
	class Vec4;

	class TB_DLL_FUNC Mat4x4
	{

	public:

		enum Field
		{
			X1 = 0,  Y1 = 1,  Z1 = 2,  WX = 3,
			X2 = 4,  Y2 = 5,  Z2 = 6,  WY = 7,
			X3 = 8,  Y3 = 9,  Z3 = 10, WZ = 11,
			TX = 12, TY = 13, TZ = 14, CZ = 15,

			SX = 0, 
			         SY = 5, 
			                  SZ = 10
		};

		// ================================
		// Static methods
		// ================================

		static Mat4x4 _IDENTITY;
		static Mat4x4 _ROTATEX(float a_Degrees);
		static Mat4x4 _ROTATEY(float a_Degrees);
		static Mat4x4 _ROTATEZ(float a_Degrees);
		static Mat4x4 _SCALE(float a_Scale);
		static Mat4x4 _TRANSLATE(float a_X, float a_Y, float a_Z);

		// ================================
		// Constructors
		// ================================

		Mat4x4();
		Mat4x4(float* a_Data);
		Mat4x4 (
			float a_X1, float a_Y1, float a_Z1, float a_ScaleX,
			float a_X2, float a_Y2, float a_Z2, float a_ScaleY,
			float a_X3, float a_Y3, float a_Z3, float a_ScaleZ,
			float a_TranslateX, float a_TranslateY, float a_TranslateZ, float a_Corner
		);
		~Mat4x4();

		// ================================
		// Conversion
		// ================================

		Mat4x4& FromEulerAngles(float a_DegreesX, float a_DegreesY, float a_DegreesZ);
		Mat4x4& FromEulerAngles(const Vec3& a_Rotation);
		Mat4x4& FromLookAt(const Vec3& a_Position, const Vec3& a_Target, const Vec3& a_Up);
		Mat4x4& FromNormal(const Vec3& a_Normal, const Vec3& a_Up);
		Mat4x4& FromAxisAngle(const Vec3& a_Axis, float a_Degrees);
		Mat4x4& FromQuaternion(float a_X, float a_Y, float a_Z, float a_W);
		Mat4x4& FromOpenGLMatrix(float* a_Source);

		//! Construct a perspective Mat4x4.
		Mat4x4& FromPerspective(float a_FOVy, float a_AspectRatio, float a_ZNear, float a_ZFar);
		Mat4x4& FromOrthographic(float a_Left, float a_Right, float a_Top, float a_Bottom, float a_ZNear, float a_ZFar);
		Mat4x4& FromFrustum(float a_Left, float a_Right, float a_Top, float a_Bottom, float a_ZNear, float a_ZFar);

		Mat4x4& Decompose(Vec3& a_Translation, Vec3& a_Rotation, Vec3& a_Scale) const;
		Mat4x4& Decompose(Quat& a_Quat, Vec3& a_Translation) const;

		Mat4x4& ToAxisAngle(Vec3& a_Axis, float& a_Angle);
		Quat ToQuaternion() const;
		Mat3x3 ToMat3x3() const;

		Mat4x4& ToOpenGLMatrix(float* a_Target) const;
		Mat4x4& ToLeftHandedMatrix(float* a_Target) const;
		Mat4x4& ToRowMajorMatrix(float* a_Target) const;

		// ================================
		// Hooks
		// ================================

		float GetDeterminant() const;

		// ================================
		// Assignment & Access
		// ================================

		Mat4x4& operator = (const float* a_Values);
		Mat4x4& operator = (const Mat4x4& a_Other);
		Mat4x4& operator = (const Mat3x3& a_Other);
		Mat4x4& operator = (const Quat& a_Other);

		//! Returns a handle to a single value
		float& operator [] (int a_Index);
		operator float* ();

		// ================================
		// Rotation
		// ================================

		Quat GetRotation() const;

		Mat4x4& AddRotationX(float a_Degrees);
		Mat4x4& AddRotationY(float a_Degrees);
		Mat4x4& AddRotationZ(float a_Degrees);		
		Mat4x4& AddRotation(const Quat& a_Rotation);

		Mat4x4& SetRotationX(float a_Degrees);
		Mat4x4& SetRotationY(float a_Degrees);
		Mat4x4& SetRotationZ(float a_Degrees);
		Mat4x4& SetRotation(const Quat& a_Rotation);

		// ================================
		// Translation
		// ================================

		Vec3 GetTranslation() const;

		Mat4x4& SetTranslationX(float a_X);
		Mat4x4& SetTranslationY(float a_Y);
		Mat4x4& SetTranslationZ(float a_Z);
		Mat4x4& SetTranslation(float a_X, float a_Y, float a_Z);
		Mat4x4& SetTranslation(const Vec3& a_Translation);

		Mat4x4& AddTranslationX(float a_X);
		Mat4x4& AddTranslationY(float a_Y);
		Mat4x4& AddTranslationZ(float a_Z);
		Mat4x4& AddTranslation(float a_X, float a_Y, float a_Z);
		Mat4x4& AddTranslation(const Vec3& a_Translation);

		// ================================
		// Scale
		// ================================

		Vec3 GetScale() const;

		Mat4x4& SetScale(float a_Scale);
		Mat4x4& SetScale(const Vec3& a_Scale);

		Mat4x4& AddScale(float a_Scale);
		Mat4x4& AddScale(const Vec3& a_Scale);

		// ================================
		// Interpolation
		// ================================

		Mat4x4& Lerp(const Mat4x4& a_Other, float a_Percentage);
		Mat4x4 GetLerp(const Mat4x4& a_Other, float a_Percentage);

		Mat4x4 GetSlerp(const Mat4x4& a_Other, float a_Percentage);

		// ================================
		// Operations
		// ================================

		Vec3 operator * (const Vec3& a_Vector) const;
		Vec4 operator * (const Vec4& a_Vector) const;

		Mat4x4 operator + (const Mat4x4& a_Other) const;
		Mat4x4& operator += (const Mat4x4& a_Other);

		Mat4x4 operator * (const Mat4x4& a_Other) const;
		Mat4x4& operator *= (const Mat4x4& a_Other);

		Mat4x4 operator * (float a_Value) const;
		Mat4x4& operator *= (float a_Value);

		Mat4x4 operator / (float a_Value) const;
		Mat4x4& operator /= (float a_Value);

		// ================================
		// Comparisons
		// ================================

		bool operator == (const Mat4x4& a_Other) const;
		bool operator == (float* a_Values) const;

		bool operator != (const Mat4x4& a_Other) const;

		bool Compare(const Mat4x4& a_Other, float a_Delta) const;
		bool Compare(float* a_Values, float a_Delta) const;

		bool IsIdentity() const;

		// ================================
		// Functions
		// ================================

		void Clear();
		Mat4x4& Identity();

		Mat4x4& Concatenate(const Mat4x4& a_Other);

		Mat4x4& Transpose();
		Mat4x4 GetTransposed() const;

		Mat4x4& Invert();
		Mat4x4 GetInverted() const;

		Mat4x4& InvertedTransposed();
		Mat4x4 GetInvertedTransposed() const;

		Vec3 Transform(const Vec3& a_Vector) const;
		Vec3 TransformRotation(const Vec3& a_Vector) const;
		Vec3 TransformHomogenized(const Vec3& a_Vector) const;

		// ================================
		// Data
		// ================================

		float values[16];

	}; // class Mat4x4

}; // namespace tb

#include "TBMat4x4.inl"

#endif